/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package org.meanzoft.m3d.model.wavefront;

import org.meanzoft.m3d.scene.SceneNode;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.List;
import org.meanzoft.m3d.resource.ResourceManager;
import org.meanzoft.m3d.scene.Renderable;
import org.meanzoft.m3d.texture.Texture;
import static org.lwjgl.opengl.GL11.*;

/**
 *
 * @author Meanz
 */
public class Model extends SceneNode implements Renderable {

    public List<Material> materials = new ArrayList<Material>();
    public List<Group> groups = new ArrayList<Group>();
    public List<Vec4> vertices = new ArrayList<Vec4>();
    public List<Vec4> normals = new ArrayList<Vec4>();
    public List<TexCoord> texCoords = new ArrayList<TexCoord>();
    public String modelFilename;
    public String textureBasePath;
    public boolean renderModel = true;
    public boolean centerModel = false;
    public boolean renderModelBounds = false;
    public boolean unitizeSize = false;
    public boolean useTexture = true;
    public boolean renderAsWireframe = false;
    public boolean useLighting = true;
    public Bounds bounds = null;

    public Model(String source, String textureBasePath) {
        this.modelFilename = source;
        this.textureBasePath = textureBasePath;
    }

    public void updateBounds() {
        bounds = new Bounds();
        for (Vec4 v : vertices) {
            bounds.calc(v);
        }
    }

    public void center() {
        centerAt(0, 0, 0);
        updateBounds();
    }

    public void centerAt(float posX, float posY, float posZ) {
        Vec4 center = bounds.getCenterPoint();

        for (Vec4 v : vertices) {
            v.x += posX - center.x;
            v.y += posY - center.y;
            v.z += posZ - center.z;
        }
        updateBounds();
    }

    public void scaleX(float maxSize) {
        float rs = maxSize / bounds.getSize().x;

        for (Vec4 v : vertices) {
            v.x *= rs;
            v.y *= rs;
            v.z *= rs;
        }
        updateBounds();
    }

    public void scaleY(float maxSize) {
        float rs = maxSize / bounds.getSize().y;

        for (Vec4 v : vertices) {
            v.x *= rs;
            v.y *= rs;
            v.z *= rs;
        }
        updateBounds();
    }

    public void scaleZ(float maxSize) {
        float rs = maxSize / bounds.getSize().z;

        for (Vec4 v : vertices) {
            v.x *= rs;
            v.y *= rs;
            v.z *= rs;
        }
        updateBounds();
    }

    public void scaleDiag(float maxSize) {
        float rs = maxSize / (2 * bounds.getRadius());

        for (Vec4 v : vertices) {
            v.x *= rs;
            v.y *= rs;
            v.z *= rs;
        }
        updateBounds();
    }

    public void update() {
    }

    public void render() {
        // save current state variables
        //glPushAttrib(GL_ALL_ATTRIB_BITS);
        {
            // check wireframe
            if (renderAsWireframe) {
                //glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
            } else {
                //glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
            }

            glDisable(GL_COLOR_MATERIAL);

            glPushMatrix();
            {

                // check for unit size model
                if (unitizeSize) {
                    float scale = 1.0f / bounds.getRadius();
                    glScalef(scale, scale, scale);
                }

                if (centerModel) {
                    Vec4 center = bounds.getCenterPoint();
                    glTranslatef(-center.x, -center.y, -center.z);
                }

                if (renderModel) {
                    glCallList(displayList);
                }

                if (renderModelBounds) {
                    // Disabled lighting for drawing the boundary lines so they are all white (or whatever I chose)
                    glDisable(GL_LIGHTING);
                    glCallList(modelBoundsList);
                }
            }
            glPopMatrix();

            // restore current state variables
        }
        //glPopAttrib();
    }
    private int displayList = -1;
    private int modelBoundsList = -1;

    public void compile() {
        int numMaterials = materials.size();
        for (int i = 0; i < numMaterials; i++) {
            if (materials.get(i).strFile != null) {
                ResourceManager.getManager().loadTexture("" + i, materials.get(i).strFile);
                materials.get(i).textureId = i;
            }
        }
        displayList = glGenLists(2);
        glNewList(displayList, GL_COMPILE);
        {
            genModelList();
        }
        glEndList();
        modelBoundsList = displayList + 1;
        //glNewList(modelBoundsList, GL_COMPILE);
        //genModelBoundsList();
        //glEndList();
    }

    public void genModelList() {
        for (Group g : groups) {
            genGroupList(g);
        }
        //glColor3f(1.0f, 1.0f, 1.0f);
    }

    public void genGroupList(Group group) {
        for (Mesh mesh : group.meshes) {
            genMeshList(group, mesh);
        }
    }

    protected void genMeshList(Group group, Mesh mesh) {
        if (mesh.faces.size() == 0) {
            return;
        }
        if (mesh.materialID < materials.size()) {
            float[] rgba = new float[4];

            Material material = materials.get(mesh.materialID);
            FloatBuffer fb = ByteBuffer.allocateDirect(16).order(ByteOrder.nativeOrder()).asFloatBuffer();
            //Diffuse color
            material.diffuseColor.getRGBComponents(rgba);
            fb.put(rgba).flip();
            glMaterial(GL_FRONT, GL_DIFFUSE, fb);
            //Ambient color
            fb.clear();
            material.ambientColor.getRGBComponents(rgba);
            fb.put(rgba).flip();
            glMaterial(GL_FRONT, GL_AMBIENT, fb);
            //Specular color
            fb.clear();
            material.specularColor.getRGBComponents(rgba);
            fb.put(rgba).flip();
            glMaterial(GL_FRONT, GL_SPECULAR, fb);
            //Shinyness
            glMaterialf(GL_FRONT, GL_SHININESS, material.shininess);
            //Emmisive color
            fb.clear();
            material.emissiveColor.getRGBComponents(rgba);
            fb.put(rgba).flip();
            glMaterial(GL_FRONT, GL_EMISSION, fb);
        }

        if (mesh.hasTexture && ResourceManager.getManager().getTexture("" + mesh.materialID) != null) {
            Texture t = ResourceManager.getManager().getTexture("" + mesh.materialID);

            // switch to texture mode and push a new matrix on the stack
            glMatrixMode(GL_TEXTURE);
            glPushMatrix();

            // check to see if the texture needs flipping
            if (true) {
                glScaled(1, -1, 1);
                glTranslated(0, -1, 0);
            }

            // switch to modelview matrix and push a new matrix on the stack
            glMatrixMode(GL_MODELVIEW);
            glPushMatrix();

            // enable, bind and get texture coordinates
            t.enable();
            t.bind();

            // This is required to repeat textures..
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
            glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
            glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
            glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
        }

        for (Face face : mesh.faces) {
            int indexType = 0;
            int vertexIndex = 0;
            int normalIndex = 0;
            int textureIndex = 0;

            glBegin(GL_POLYGON);
            {
                // TODO: the number of vertices for a face is not always 3
                for (int whichVertex = 0; whichVertex < face.vertIndex.length; whichVertex++) {
                    vertexIndex = face.vertIndex[whichVertex];
                    try {
                        normalIndex = face.normalIndex[whichVertex];

                        indexType = 0;
                        glNormal3f(normals.get(normalIndex).x, normals.get(normalIndex).y,
                                normals.get(normalIndex).z);

                        if (mesh.hasTexture) {
                            if (texCoords.size() > 0) {
                                textureIndex = face.coordIndex[whichVertex];
                                indexType = 1;
                                glTexCoord2f(texCoords.get(textureIndex).u, texCoords.get(textureIndex).v);
                            }
                        }
                        indexType = 2;
                        glVertex3f(vertices.get(vertexIndex).x, vertices.get(vertexIndex).y,
                                vertices.get(vertexIndex).z);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
            glEnd();
        }

        if (mesh.hasTexture && ResourceManager.getManager().getTexture("" + mesh.materialID) != null) {
            Texture t = ResourceManager.getManager().getTexture("" + mesh.materialID);
            t.disable();

            glMatrixMode(GL_TEXTURE);
            glPopMatrix();

            glMatrixMode(GL_MODELVIEW);
            glPopMatrix();
        }
    }
}
